package cn.tedu.csmall.product.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect // 将当前类标记为“切面”类
@Component // 将当前类标记为“组件”类
public class TimerAspect {

    public TimerAspect() {
        log.debug("创建切面对象：TimerAspect");
    }

    // 【AOP的核心概念】
    // 连接点（JoinPoint）：数据处理过程中的某个节点，可能是调用了方法，或抛出了异常
    // 切入点（PointCut）：选择1个或多个连接点的表达式
    // ---------------------------------------------------------
    // 【通知（Advice）注解】
    // @Before：表示“在……之前”，方法应该是无参数的
    // @After：表示“在……之后”，无论顺利执行结束，还是抛出异常，都会执行，方法应该是无参数的
    // @AfterReturning：表示“在返回结果之后”，方法的参数是JoinPoint和返回值对象
    // @AfterThrowing：表示“在抛出异常之后”，方法的参数是JoinPoint和异常对象
    // @Around：表示“包裹”，也称之“环绕”，使用此注解时，方法的参数必须是ProceedingJoinPoint类型的
    // ---------------------------------------------------------
    // @Around开始
    // try {
    //    @Before
    //    连接点方法
    //    @AfterReturning
    // } catch (Throwable e) {
    //    @AfterThrowing
    // } finally {
    //    @After
    // }
    // @Around结束
    // ---------------------------------------------------------
    // 注解中的execution内部配置表达式，以匹配需要在哪些方法上执行切面代码
    // 在表达式中，星号（*）是通配符，可以匹配1次任意内容
    // 例如，在返回值位置使用星号，表示此方法可以是任意返回值，包括：void / int / String ...
    // 在表达式中，连续的2个小数点（..）也是通配符，可以匹配0~n次任意内容，仅能用于包名和参数列表部分
    // 例如，在方法的参数列表使用2个小数点，表示此方法的参数列表中可以是0个参数，也可以是n个参数
    @Around("execution(* cn.tedu.csmall.product.service.*.*(..))")
    //                 ↑ 此星号表示方法的返回值类型
    //                   ↑----------- 根包 ------------↑
    //                                                  ↑ 类名
    //                                                    ↑ 方法名
    //                                                       ↑↑ 参数列表
    // 提示：在“返回值类型”的左侧，还可以配置修饰符，例如方法的注解等，修饰符是可选的配置
    public Object timer(ProceedingJoinPoint pjp) throws Throwable {
        log.debug("执行了TimerAspect中的方法……");
        log.debug("【{}】类型的对象调用了【{}】方法，传入的参数为【{}】",
                pjp.getTarget().getClass().getName(),
                pjp.getSignature().getName(),
                pjp.getArgs());

        long start = System.currentTimeMillis();

        // 调用ProceedingJoinPoint的proceed()方法，本质就是执行了连接点对应的方法
        // 注意：调用的proceed()方法会抛出Throwable，此时必须抛出，不可以使用try...catch捕获并处理
        // 注意：调用proceed()方法时必须获取返回值，此返回值就是连接点对应的方法的返回值，必须作为当前切面方法的返回值
        Object result = pjp.proceed();

        long end = System.currentTimeMillis();
        log.debug("执行耗时：{}ms", end - start);

        return result;
    }

}
